/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package desktopapplication1;

import java.util.*;
import java.util.Map.Entry;


/**
 *
 * @author TEST
 */
public class Game {
    
    private Integer numberOfGenerations;
    private Integer numberOfPlayers;
    private Integer numberOfRounds;
    private Integer numberOfLastMoves;
    private Integer strategy; //0-wet za wet, 1-zawsze oszukuj, 2-zawsze wspolpracuj, 3-gracz vs gracz
    private Integer playerProfitCC = 20;
    private Integer playerProfitCD = 0;
    private Integer playerProfitDC = 30;
    private Integer playerProfitDD = 10;
    private Random generator = new Random();
    private Map<Integer,Map<String,Double>> playersProbabilities;
    private Map<Integer,String> playersMoves;
    private Map<Integer,Integer> playersPoints;
    private List<Integer> totalPointsInPopulations;
    
    
    
    public Game(Integer newNumberOfGenerations, Integer newNumberOfPlayers, Integer newNumberOfRounds, Integer newNumberOfLastMoves, Integer newStrategy) {
        numberOfGenerations = newNumberOfGenerations;
        numberOfPlayers = newNumberOfPlayers;
        numberOfRounds = newNumberOfRounds;
        numberOfLastMoves = newNumberOfLastMoves;
        strategy = newStrategy;
        playersProbabilities = new HashMap<Integer,Map<String,Double>>();
        MapOfProbabilities mapOfProbabilities;
        for(int playerNumber = 0; playerNumber < numberOfPlayers; playerNumber++) {
            mapOfProbabilities = new MapOfProbabilities(newNumberOfLastMoves);
            Map<String,Double> probabilities = mapOfProbabilities.getMapOfProbabilities();
            
            playersProbabilities.put(playerNumber, probabilities);
        }
        playersMoves = new HashMap<Integer,String>();
        playersPoints = new HashMap<Integer,Integer>();
        totalPointsInPopulations = new ArrayList<Integer>();
    }
    
    
    public void setNumberOfPopulations(Integer newNumberOfPopulations) {
        numberOfPlayers = newNumberOfPopulations;
    }
    
    public Integer getNumberOfGenerations() {
        return numberOfGenerations;
    }
    
    public void setNumberOfPlayers(Integer newNumberOfPlayers) {
        numberOfPlayers = newNumberOfPlayers;
    }
    
    public Integer getNumberOfPlayers() {
        return numberOfPlayers;
    }
    
    public void setNumberOfRounds(Integer newNumberOfRounds) {
        numberOfRounds = newNumberOfRounds;
    }
    
    public Integer getNumberOfRounds() {
        return numberOfRounds;
    }
    
    public void setNumberOfLastMoves(Integer newNumberOfLastMoves) {
        numberOfLastMoves = newNumberOfLastMoves;
    }
    
    public Integer getNumberOfLastMoves() {
        return numberOfLastMoves;
    }
    
    public void setStrategy(Integer newStrategy) {
        strategy = newStrategy;
    }
    
    public Integer getStrategy() {
        return strategy;
    }
    
    public Map<Integer,Map<String,Double>> getPlayersProbabilities() {
        return playersProbabilities;
    }
    
    public Map<Integer,String> getPlayersMoves() {
        return playersMoves;
    }
    
    public Map<Integer,Integer> getPlayersPoints() {
        return playersPoints;
    }
     
    public List<Integer> getTotalPointsInPopulations() {
        return totalPointsInPopulations;
    }
    
    public String getRandomMove() {
        String alphabet = "CD";
        char move = alphabet.charAt(generator.nextInt(alphabet.length()));
        return Character.toString(move);
    }

    public Integer getPlayersTotalPoints() {
        Integer totalPoints = 0;
        for(Integer points:playersPoints.values()) {
            totalPoints += points;
        }
        return totalPoints;
    }
    
    //sortowanie wartosci w mapie na podstawie: http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java
    public Map sortMapByValue(Map map) {
        List list = new LinkedList(map.entrySet());
        Collections.sort(list, new Comparator() {
             public int compare(Object o1, Object o2) {
                  return ((Comparable) ((Map.Entry) (o1)).getValue())
                 .compareTo(((Map.Entry) (o2)).getValue());
             }
        });

        Map result = new LinkedHashMap();
        for (Iterator it = list.iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry)it.next();
            result.put(entry.getKey(), entry.getValue());
        }
        return result;
    } 
    
    public Integer getRandomPlayerOpponent(int playerNumber) {
        Integer opponentNumber = null;
        while(true) {            
            opponentNumber = generator.nextInt(numberOfPlayers);
            if(opponentNumber != playerNumber) break;
        }
        return opponentNumber;
    }
    
    
    public void startGame() {
        createInitialPopulation();
        //wygenerowanie nowych populacji:
        for(int populationNumber = 0; populationNumber < numberOfGenerations; populationNumber++) { 
            createNewPopulation();
        }
    }
    
    
    private void createInitialPopulation() {       
        for(int playerNumber = 0; playerNumber < numberOfPlayers; playerNumber++) {  
            StringBuilder firstPlayerMoves = new StringBuilder();
            StringBuilder secondPlayerMoves = new StringBuilder();
            String firstPlayerMove = null;
            String secondPlayerMove = null; 
            Integer firstPlayerPoints = 0;
            Integer randomSecondPlayer = null;
            int lastMoveNumber;
            for(lastMoveNumber = 0; lastMoveNumber < numberOfLastMoves; lastMoveNumber++) {
                firstPlayerMove = getRandomMove();
                
                if(strategy == 0) {
                    if(lastMoveNumber == 0) secondPlayerMove = "C";
                    else secondPlayerMove = Character.toString(firstPlayerMoves.charAt(lastMoveNumber-1));
                }
                else if(strategy == 1) secondPlayerMove = "D"; 
                else if(strategy == 2) secondPlayerMove = "C";
                else secondPlayerMove = getRandomMove();
                
                firstPlayerMoves.append(firstPlayerMove);
                secondPlayerMoves.append(secondPlayerMove);

                if(firstPlayerMove.equals("C") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitCC;
                else if(firstPlayerMove.equals("C") && secondPlayerMove.equals("D")) firstPlayerPoints += playerProfitCD;
                else if(firstPlayerMove.equals("D") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitDC;
                else firstPlayerPoints += playerProfitDD;
            }     
            
            if(strategy == 3) randomSecondPlayer = getRandomPlayerOpponent(playerNumber);

            for(; lastMoveNumber < numberOfRounds; lastMoveNumber++) {   
                String firstPlayerLastMoves = firstPlayerMoves.substring(firstPlayerMoves.length() - numberOfLastMoves);
                String secondPlayerLastMoves = secondPlayerMoves.substring(secondPlayerMoves.length() - numberOfLastMoves);

                if(playersProbabilities.get(playerNumber).get(secondPlayerLastMoves) > 0.5) firstPlayerMove = "C";
                else firstPlayerMove = "D";

                if(strategy == 0) {
                    if(lastMoveNumber == 0) secondPlayerMove = "C";
                    else secondPlayerMove = Character.toString(firstPlayerMoves.charAt(lastMoveNumber-1));
                }
                else if(strategy == 1) secondPlayerMove = "D"; 
                else if(strategy == 2) secondPlayerMove = "C";
                else {
                    if(playersProbabilities.get(randomSecondPlayer).get(firstPlayerLastMoves) > 0.5) secondPlayerMove = "C";
                    else secondPlayerMove = "D";
                }

                firstPlayerMoves.append(firstPlayerMove);
                secondPlayerMoves.append(secondPlayerMove);

                if(firstPlayerMove.equals("C") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitCC;
                else if(firstPlayerMove.equals("C") && secondPlayerMove.equals("D")) firstPlayerPoints += playerProfitCD;
                else if(firstPlayerMove.equals("D") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitDC;
                else firstPlayerPoints += playerProfitDD;
            }

            playersMoves.put(playerNumber, firstPlayerMoves.toString());
            playersPoints.put(playerNumber, firstPlayerPoints);
        }
        totalPointsInPopulations.add(getPlayersTotalPoints());
    }
    
    
    private void createNewPopulation() {
        //sortowanie rosnaco mapy wedlug wartosci:        
        Map<Integer,Integer> sortedPlayersPoints = sortMapByValue(playersPoints);

        //liczba nowych osobnikow w nowej populacji - 50%:
        Integer numberOfNewPlayers = numberOfPlayers/2;
        
        //nowa populacja:
        Map<Integer,String> playersMovesInNewPopulation = new HashMap<Integer,String>();
        
        int playerNumber = 0;
        int rand;
        List<Integer> theBestPlayers = new ArrayList<Integer>();
                
        //pobranie 50% najlepszych osobnikow obecnej populacji do nowej populacji:
        for(Entry<Integer,Integer> entry : sortedPlayersPoints.entrySet()) {
            if(playerNumber >= numberOfNewPlayers) {
                playersMovesInNewPopulation.put(playerNumber, playersMoves.get(entry.getKey()));                 
                theBestPlayers.add(entry.getKey());
            }
            playerNumber++;
        } 

        //wygenerowanie 50% nowych osobnikow do nowej populacji - krzyzowanie (metoda rankingowa) i mutacja:        
        for(playerNumber = 0; playerNumber < numberOfNewPlayers; playerNumber++) {
            //mutacja - szansa 1 do 10:
            rand = generator.nextInt(10);
            if(rand == 0) {
                Integer mutationPoint = generator.nextInt(numberOfRounds);
                List<Integer> tempTheBestPlayers = new ArrayList<Integer>();
                tempTheBestPlayers.addAll(theBestPlayers);
                rand = generator.nextInt(tempTheBestPlayers.size());            
                String parentMoves = playersMoves.get(tempTheBestPlayers.get(rand));
                char[] charArray = parentMoves.toCharArray();
                if(charArray[mutationPoint] == 'C') charArray[mutationPoint] = 'D';
                else charArray[mutationPoint] = 'C';
                String childMoves = new String(charArray);
                
                playersMovesInNewPopulation.put(playerNumber, childMoves);
            }
            else { //krzyzowanie:
                Integer crossoverPoint = generator.nextInt(numberOfRounds); 
                List<Integer> tempTheBestPlayers = new ArrayList<Integer>();
                tempTheBestPlayers.addAll(theBestPlayers);
                rand = generator.nextInt(tempTheBestPlayers.size());            
                String firstParentMoves = playersMoves.get(tempTheBestPlayers.get(rand));
                tempTheBestPlayers.remove(rand); //usuniecie wykorzystanego osobnika zeby nie krzyzowal sie sam ze soba
                rand = generator.nextInt(tempTheBestPlayers.size());            
                String secondParentMoves = playersMoves.get(tempTheBestPlayers.get(rand));
                String childMoves = firstParentMoves.substring(0, crossoverPoint) + secondParentMoves.substring(crossoverPoint);

                playersMovesInNewPopulation.put(playerNumber, childMoves);
            }
        }
        playersMoves.clear();
        playersMoves = playersMovesInNewPopulation;

        //obliczenie punktow osobnikow w nowej populacji:        
        playersPoints.clear(); 
        for(playerNumber = 0; playerNumber < numberOfPlayers; playerNumber++) {  
            String firstPlayerMove = null;
            String secondPlayerMove = null; 
            Integer firstPlayerPoints = 0; 
            Integer randomSecondPlayer = null;
            if(strategy == 3) randomSecondPlayer = getRandomPlayerOpponent(playerNumber);
            for(int roundNumber = 0; roundNumber < numberOfRounds; roundNumber++) {  
                firstPlayerMove = Character.toString(playersMoves.get(playerNumber).charAt(roundNumber));
                if(strategy == 0) {
                    if(roundNumber == 0) secondPlayerMove = "C";
                    else secondPlayerMove = Character.toString(playersMoves.get(playerNumber).charAt(roundNumber-1));
                }
                else if(strategy == 1) secondPlayerMove = "D"; 
                else if(strategy == 2) secondPlayerMove = "C";
                else secondPlayerMove = Character.toString(playersMoves.get(randomSecondPlayer).charAt(roundNumber));

                if(firstPlayerMove.equals("C") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitCC;
                else if(firstPlayerMove.equals("C") && secondPlayerMove.equals("D")) firstPlayerPoints += playerProfitCD;
                else if(firstPlayerMove.equals("D") && secondPlayerMove.equals("C")) firstPlayerPoints += playerProfitDC;
                else firstPlayerPoints += playerProfitDD;
            }            
            playersPoints.put(playerNumber, firstPlayerPoints);
        } 
        totalPointsInPopulations.add(getPlayersTotalPoints());
    }
    
    
}
